using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.ComponentModel;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Workflows.Components.DALC;
using Workflows.Components.FALC;

namespace Workflows.Components
{
    /// <summary></summary>
    [Serializable]
    public class WorkflowDesign
    {
        /// <summary>
        /// 
        /// </summary>
        public event OnPropertyChanged OnPropertyChangedEvent;

        #region fields
        // private string _DesignDocumentFilePath;
        private Guid _WorkflowGuid;
        private string _WorkflowName;
        private Version _Version;
        private DateTime _CreationTime;
        private DateTime _ModificationTime;
        private string _Author;
        private string _Description;
        private string _FailureNotifyEmailAddress;
        private Dictionary<Guid, WorkflowComponentBase> _Components;
        private Dictionary<Guid, Connector> _Connectors;
        private GlobalVariable[] _GlobalVariables;
        private DateTime _CheckOutTime;
        private string _CheckOutBy;
        private bool _IsLocked;
        private string _WoxFilePath;
        #endregion

        #region ctor
        /// <summary></summary>
        public WorkflowDesign()
        {
            this._Author = Environment.UserName;
            this._CreationTime = DateTime.Now;
            this._ModificationTime = DateTime.Now;
            this._Description = "workflow description";
            this._FailureNotifyEmailAddress = Environment.UserName + "@" + Environment.UserDomainName + ".com";
            this._Version = new Version(1, 0, 0, 0);
            this._WorkflowGuid = Guid.NewGuid();
            this._WorkflowName = "New Workflow";
            this._Components = new Dictionary<Guid, WorkflowComponentBase>();
            this._Connectors = new Dictionary<Guid, Connector>();
            this._WoxFilePath = FileUtil.CreateNewWindowsAppTempFilePath("Workflow", "New Workflow", ".wox");
            this._CheckOutTime = DateTime.Now;
            this._CheckOutBy = Environment.UserName;
            this._IsLocked = true;

            this._GlobalVariables = null;
        }

        /// <summary></summary>
        public WorkflowDesign(string woxFilePath)
        {
            // this._DesignDocumentFilePath = filePath;
            if (File.Exists(woxFilePath))
            {
                string folderPath = Path.GetDirectoryName(woxFilePath);
                string fileName = Path.GetFileNameWithoutExtension(woxFilePath);
                string xmlFilePath = Path.Combine(folderPath, fileName + ".xml");
                if(File.Exists(xmlFilePath))
                {
                    XmlDocument xDoc = new XmlDocument();
                    xDoc.Load(xmlFilePath);
                    WorkflowDesign wfDesign = this.ReadFromXml(xDoc);
                    

                    this.CheckoutWorkflowDesign(wfDesign);
                }
            }
            else
            {
                this._GlobalVariables = null;
            }
        }

        /// <summary>
        /// load from database
        /// </summary>
        /// <param name="workflowID"></param>
        public WorkflowDesign (Guid workflowID)
        {
            WorkflowDesign wfDesign = WorkflowDesignDALC.GetWorkflowDesign(workflowID);
            this.CheckoutWorkflowDesign(wfDesign);
        }
        #endregion

        #region components
        /// <summary></summary>
        public void AddComponent(WorkflowComponentBase component)
        {
            if (!this._Components.ContainsKey(component.ComponentID))
            {
                if (this.HasTriggerComponent() && this.IsComponentTrigger(component))
                {
                    throw new Exception("Workflow can only have one trigger component");
                }
                this._Components.Add(component.ComponentID, component);
            }
            else
            {
                throw new Exception("The component has already been added");
            }
        }

        /// <summary></summary>
        public void RemoveComponent(Guid guid)
        {
            this._Components.Remove(guid);
        }

        /// <summary></summary>
        public void UpdateComponent(WorkflowComponentBase component)
        {
            if (this._Components.ContainsKey(component.ComponentID))
            {
                this._Components[component.ComponentID] = component;
            }
        }
        private bool HasTriggerComponent()
        {
            bool hasTrigger = false;
            if (this._Components.Count > 0)
            {
                WorkflowComponentBase[] components = new WorkflowComponentBase[this._Components.Count];
                this._Components.Values.CopyTo(components, 0);
                for (int i = 0; i < components.Length; i++)
                {
                    if (this.IsComponentTrigger(components[i]))
                    {
                        hasTrigger = true;
                        break;
                    }
                }
            }
            return hasTrigger;
        }
        private bool IsComponentTrigger(WorkflowComponentBase component)
        {
            if (component.GetType().IsSubclassOf(typeof(WorkflowTriggerBase)))
                return true;
            else
                return false;
        }

        /// <summary></summary>
        public WorkflowTriggerBase GetTriggerComponent()
        {
            if (this._Components != null && this._Components.Count > 0)
            {
                WorkflowComponentBase[] components = new WorkflowComponentBase[this._Components.Count];
                this._Components.Values.CopyTo(components, 0);
                for (int i = 0; i < components.Length; i++)
                {
                    if (this.IsComponentTrigger(components[i]))
                        return components[i] as WorkflowTriggerBase;
                }
            }
            return null;
        }
        #endregion

        #region connectors
        /// <summary></summary>
        public void AddConnector(Connector connector)
        {
            if (!this._Connectors.ContainsKey(connector.ConnectorID))
            {
                this._Connectors.Add(connector.ConnectorID, connector);
            }
        }

        /// <summary></summary>
        public void RemoveConnector(Connector connector)
        {
            this._Connectors.Remove(connector.ConnectorID);
        }

        /// <summary></summary>
        public void UpdateConnector(Connector connector)
        {
            if (this._Connectors.ContainsKey(connector.ConnectorID))
            {
                this._Connectors[connector.ConnectorID] = connector;
            }
        }
        #endregion

        #region global variables
        /// <summary></summary>
        public void AddVariable(string name, object value)
        {
            if(this._GlobalVariables==null || this._GlobalVariables.Length==0)
            {
                this._GlobalVariables = new GlobalVariable[] { new GlobalVariable(name, SimpleDataType.TextType, value) };
            }
            else
            {
                bool exist = false;
                for (int i = 0; i < this._GlobalVariables.Length;i++ )
                {
                    if(this._GlobalVariables[i].VarName.ToLower()==name.ToLower())
                    {
                        exist = true;
                        this._GlobalVariables[i].VarValue = value.ToString();
                        break;
                    }
                }
                if(!exist)
                {
                    List<GlobalVariable> globalVarList=new List<GlobalVariable>();
                    globalVarList.AddRange(this._GlobalVariables);
                    globalVarList.Add(new GlobalVariable(name, SimpleDataType.TextType, value));
                    this._GlobalVariables = globalVarList.ToArray();
                }
            }
        }

        /// <summary></summary>
        public void RemoveVariable(string name)
        {
            List<GlobalVariable> globalVarList=new List<GlobalVariable>();
            if(this._GlobalVariables !=null && this._GlobalVariables.Length>0)
            {
                for(int i=0;i<this._GlobalVariables.Length;i++)
                {
                    if(this._GlobalVariables[i].VarName.ToLower()!=name.ToLower())
                    {
                        globalVarList.Add(this._GlobalVariables[i]);
                    }
                }
            }
            this._GlobalVariables = globalVarList.ToArray();
        }
        #endregion

        #region props
        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public string DesignDocumentFilePath
        {
            get
            {
                return this._WoxFilePath;
            }
            set
            {
                this._WoxFilePath = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public int ComponentCount
        {
            get
            {
                if (this._Components != null)
                    return this._Components.Count;
                else
                    return 0;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public Guid WorkflowGuid
        {
            get
            {
                return this._WorkflowGuid;
            }
            set
            {
                this._WorkflowGuid = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public DateTime CreationTime
        {
            get
            {
                return this._CreationTime;
            }
            set
            {
                this._CreationTime = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public DateTime ModificationTime
        {
            get
            {
                return this._ModificationTime;
            }
            set
            {
                this._ModificationTime = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public Version Version
        {
            get
            {
                return this._Version;
            }
            set
            {
                this._Version = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public string Author
        {
            get
            {
                return this._Author;
            }
            set
            {
                this._Author = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public string WorkflowName
        {
            get
            {
                return this._WorkflowName;
            }
            set
            {
                this._WorkflowName = value;

                if(this.OnPropertyChangedEvent !=null)
                    this.OnPropertyChangedEvent(this, "WorkflowName");

            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public string Description
        {
            get
            {
                return this._Description;
            }
            set
            {
                this._Description = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Info")]
        public string FailureNotifyEmailAddress
        {
            get
            {
                return this._FailureNotifyEmailAddress;
            }
            set
            {
                this._FailureNotifyEmailAddress = value;
            }
        }

        /// <summary></summary>
        [Browsable(false), Category("Design")]
        public WorkflowComponentBase[] Components
        {
            get
            {
                if (this._Components != null && this._Components.Count > 0)
                {
                    WorkflowComponentBase[] components = new WorkflowComponentBase[this._Components.Count];
                    this._Components.Values.CopyTo(components, 0);
                    return components;
                }
                else
                    return null;
            }
            set
            {
                this._Components.Clear();
                if (value != null && value.Length > 0)
                {
                    for (int i = 0; i < value.Length; i++)
                    {
                        this._Components.Add(value[i].ComponentID, value[i]);
                    }
                }
            }
        }

        /// <summary></summary>
        [Browsable(false), Category("Design")]
        public Connector[] Connectors
        {
            get
            {
                if (this._Connectors != null && this._Connectors.Count > 0)
                {
                    Connector[] connectors = new Connector[this._Connectors.Count];
                    this._Connectors.Values.CopyTo(connectors, 0);
                    return connectors;
                }
                else
                    return null;
            }
            set
            {
                this._Connectors.Clear();
                if (value != null && value.Length > 0)
                {
                    for (int i = 0; i < value.Length; i++)
                    {
                        this._Connectors.Add(value[i].ConnectorID, value[i]);
                    }
                }
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public bool Enabled
        {
            get
            {
                return this.GetTriggerComponent().IsActive;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public DateTime CheckOutTime
        {
            get
            {
                return this._CheckOutTime;
            }
            set
            {
                this._CheckOutTime = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public string CheckOutBy
        {
            get
            {
                return this._CheckOutBy;
            }
            set
            {
                this._CheckOutBy = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public bool IsLocked
        {
            get
            {
                return this._IsLocked;
            }
            set
            {
                this._IsLocked = value;
            }
        }

        /// <summary></summary>
        [Browsable(true), Category("Design")]
        public GlobalVariable[] GlobalVariables
        { 
            get
            {
                return this._GlobalVariables;
            }
            set
            {
                this._GlobalVariables = value;
            }
        }
        #endregion

        #region public methods
        /// <summary></summary>
        public WorkflowMetaData CreateMetaData()
        {
            WorkflowMetaData metaData=new WorkflowMetaData();
            metaData.Author = this.Author;
            metaData.CreationTime = this.CreationTime;
            metaData.Description = this.Description;
            metaData.ModificationTime = this.ModificationTime;
            metaData.Version = this.Version;
            metaData.WorkflowGuid = this.WorkflowGuid;
            metaData.WorkflowName = this.WorkflowName;
            metaData.FailureNotifyEmailAddress = this.FailureNotifyEmailAddress;
            return metaData;
        }

        /// <summary></summary>
        public void Clear()
        {
            this._Connectors.Clear();
            this._Components.Clear();
            // this._GlobalVariables = null;
        }
        #endregion

        #region private methods
        private void CheckoutWorkflowDesign(WorkflowDesign wfDesign)
        {
            this._WorkflowGuid = wfDesign.WorkflowGuid;
            this._WorkflowName = wfDesign.WorkflowName;
            this._Version = wfDesign.Version;
            this._CreationTime = wfDesign.CreationTime;
            this._ModificationTime = wfDesign.ModificationTime;
            this._Author = wfDesign.Author;
            this._Description = wfDesign.Description;
            this._FailureNotifyEmailAddress = wfDesign.FailureNotifyEmailAddress;
            this._WoxFilePath = wfDesign.DesignDocumentFilePath;
            this._Components = new Dictionary<Guid, WorkflowComponentBase>();
            if (wfDesign.Components != null && wfDesign.Components.Length > 0)
            {
                foreach (WorkflowComponentBase wfComponent in wfDesign.Components)
                {
                    this._Components.Add(wfComponent.ComponentID, wfComponent);
                }
            }
            this._Connectors = new Dictionary<Guid, Connector>();
            if (wfDesign.Connectors != null && wfDesign.Connectors.Length > 0)
            {
                foreach (Connector wfConnector in wfDesign.Connectors)
                {
                    this._Connectors.Add(wfConnector.ConnectorID, wfConnector);
                }
            }
            this._GlobalVariables = null;
            if(wfDesign.GlobalVariables !=null && wfDesign.GlobalVariables.Length>0)
            {
                this._GlobalVariables = wfDesign.GlobalVariables;
            }
            this._CheckOutTime = DateTime.Now;
            this._CheckOutBy = Environment.UserName;
            this._IsLocked = true;
            wfDesign.CheckOutBy = this.CheckOutBy;
            wfDesign.CheckOutTime = this.CheckOutTime;
            wfDesign.IsLocked = this.IsLocked;
            WorkflowDesignDALC.CheckoutWorkflowDesign(wfDesign);
        }
        #endregion

        /// <summary></summary>
        public Connector GetConnector(WorkflowComponentBase fromComponent, WorkflowComponentBase toComponent)
        {
            foreach(Connector wfConnector in this._Connectors.Values)
            {
                if(wfConnector.FromComponentID==fromComponent.ComponentID && wfConnector.ToComponentID==toComponent.ComponentID)
                    return wfConnector;
            }
            return null;
        }

        /// <summary></summary>
        public Connector GetConnector(Guid fromComponentID, Guid toComponentID)
        {
            foreach (Connector wfConnector in this._Connectors.Values)
            {
                if (wfConnector.FromComponentID == fromComponentID && wfConnector.ToComponentID == toComponentID)
                    return wfConnector;
            }
            return null;
        }

        /// <summary></summary>
        public WorkflowComponentBase GetComponent(Guid componentID)
        {
            if (this._Components.ContainsKey(componentID))
                return this._Components[componentID];
            else
                return null;
        }

        /// <summary></summary>
        public List<Connector> GetUpstreamConnectors(Guid toComponentID)
        {
            List<Connector> upstreamConnectors=new List<Connector>();
            foreach(Connector conn in this._Connectors.Values)
            {
                if(conn.ToComponentID==toComponentID)
                    upstreamConnectors.Add(conn);
            }
            return upstreamConnectors;
        }

        /// <summary></summary>
        public XmlDocument SaveAsXml()
        {
            XmlDocument xdoc=new XmlDocument();
            xdoc.LoadXml("<WorkflowDesign></WorkflowDesign>");
            XmlNode root = xdoc.DocumentElement;
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "WorkflowGuid", this.WorkflowGuid.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "WorkflowName", this.WorkflowName);
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "Version", this.Version.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "CreationTime", this.CreationTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "ModificationTime", this.ModificationTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "Author", this.Author);
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "Description", this.Description);
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "FailureNotifyEmailAddress", this.FailureNotifyEmailAddress);
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "CheckOutBy", this.CheckOutBy);
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "CheckOutTime", this.CheckOutTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "IsLocked", this.IsLocked.ToString());
            XmlDataUtil.UpdateAttribute(ref xdoc, root, "FilePath",this.DesignDocumentFilePath);

            XmlNode componentRootNode = XmlDataUtil.AddElement(ref xdoc, root, "Components");
            if (this.Components != null && this.Components.Length > 0)
            {
                foreach (WorkflowComponentBase wfCom in this.Components)
                {
                    wfCom.Serialize(ref xdoc, ref componentRootNode);
                }
            }

            XmlNode connectorRootNode = XmlDataUtil.AddElement(ref xdoc, root, "Connectors");
            if (this.Connectors != null && this.Connectors.Length > 0)
            {
                foreach (Connector conn in this.Connectors)
                {
                    Connector.Serialize(ref xdoc, ref connectorRootNode, conn);
                }
            }

            XmlNode varRootNode = XmlDataUtil.AddElement(ref xdoc, root, "GlobalVariables");
            if(this.GlobalVariables !=null && this.GlobalVariables.Length>0)
            {
                foreach(GlobalVariable wfVar in this.GlobalVariables)
                {
                    XmlNode varNode = XmlDataUtil.AddElement(ref xdoc, varRootNode, "Variable");
                    XmlDataUtil.UpdateAttribute(ref xdoc, varNode, "VariableName", wfVar.VarName);
                    XmlDataUtil.UpdateAttribute(ref xdoc, varNode, "DataType", ((int) wfVar.DataType).ToString());
                    XmlDataUtil.UpdateAttribute(ref xdoc, varNode, "VariableValue", wfVar.VarValue.ToString());
                }
            }
            return xdoc;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xDoc"></param>
        /// <returns></returns>
        public WorkflowDesign ReadFromXml(XmlDocument xDoc)
        {
            WorkflowDesign wfDesign=new WorkflowDesign();
            XmlNode root = xDoc.DocumentElement;
            wfDesign.WorkflowGuid = new Guid(XmlDataUtil.GetAttributeValue(root,"WorkflowGuid",Guid.Empty.ToString()));
            wfDesign.WorkflowName = XmlDataUtil.GetAttributeValue(root, "WorkflowName", "");
            wfDesign.Version = new Version(XmlDataUtil.GetAttributeValue(root, "Version", "1.0.0.0"));
            wfDesign.CreationTime =
                DateTime.Parse(XmlDataUtil.GetAttributeValue(root, "CreationTime", DateTime.Now.ToString()));
            wfDesign.ModificationTime = DateTime.Parse(
                XmlDataUtil.GetAttributeValue(root, "ModificationTime", DateTime.Now.ToString()));
            wfDesign.Author = XmlDataUtil.GetAttributeValue(root, "Author", Environment.UserName);
            wfDesign.Description = XmlDataUtil.GetAttributeValue(root, "Description", "");
            wfDesign.FailureNotifyEmailAddress = XmlDataUtil.GetAttributeValue(root, "FailureNotifyEmailAddress", "");
            wfDesign.CheckOutBy = XmlDataUtil.GetAttributeValue(root, "CheckOutBy", "");
            wfDesign.CheckOutTime = DateTime.Parse(XmlDataUtil.GetAttributeValue(root, "CheckOutTime", DateTime.Now.ToString()));
            wfDesign.IsLocked = bool.Parse(XmlDataUtil.GetAttributeValue(root, "IsLocked", false.ToString()));
            wfDesign.DesignDocumentFilePath = XmlDataUtil.GetAttributeValue(root, "FilePath", "");

            XmlNodeList gVarNodes = root.SelectNodes("GlobalVariables/Variable");
            if(gVarNodes !=null)
            {
                List<GlobalVariable> gVars=new List<GlobalVariable>();
                foreach(XmlNode gNode in gVarNodes)
                {
                    string varName = XmlDataUtil.GetAttributeValue(gNode, "VariableName", "");
                    SimpleDataType dataType =
                        (SimpleDataType)
                        int.Parse(XmlDataUtil.GetAttributeValue(gNode, "DataType",
                                                                ((int) SimpleDataType.TextType).ToString()));
                    string varValue = XmlDataUtil.GetAttributeValue(gNode, "VariableValue", "");
                    GlobalVariable gVar = new GlobalVariable(varName, dataType, varValue);
                    gVars.Add(gVar);
                }
                wfDesign.GlobalVariables = gVars.ToArray();
            }

            WorkflowEngine wfEngine=new WorkflowEngine();
            wfDesign.Components = wfEngine.GetWorkflowComponents(xDoc).ToArray();
            wfDesign.Connectors = wfEngine.GetConnectors(xDoc).ToArray();

            return wfDesign;
        }
    }
}
